package edu.kpi.pzks2.simulator.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import edu.kpi.pzks2.graph.Edge;
import edu.kpi.pzks2.graph.Node;
import edu.kpi.pzks2.simulator.core.QueueBuilder;
import edu.kpi.pzks2.simulator.core.SimulationBundle;
import edu.kpi.pzks2.simulator.core.Simulator;
import edu.kpi.pzks2.simulator.core.strategy.Strategy;
import edu.kpi.pzks2.simulator.core.strategy.StrategyFactory;
import edu.kpi.pzks2.simulator.impl.NeighbourStrategy.NeighbourStrategyFactory;
import edu.kpi.pzks2.simulator.impl.SimpleRouterImpl.SimpleRouterFactory;
import edu.kpi.pzks2.simulator.links.Message;
import edu.kpi.pzks2.simulator.processing.Processor;
import edu.kpi.pzks2.simulator.processing.Task;
import edu.kpi.pzks2.simulator.results.HistoryBean;
import edu.kpi.pzks2.simulator.results.ProcHistoryBean;
import edu.kpi.pzks2.simulator.results.SimulationResult;
import edu.kpi.pzks2.simulator.results.TimeLine;
import edu.uci.ics.jung.graph.Graph;

public class PreemptiveStrategy extends BaseStrategyImpl {

    // map (task node, procId)
    private final Map<Node, Node> taskPlan;
    private final List<Pair> processedTasks;
    
    private PreemptiveStrategy(final SimulationBundle simBundle, final Graph<Node, Edge> taskGraph, final Graph<Node, Edge> procGraph, 
            final QueueBuilder queueBuilder, final Simulator simulator) {
        super(taskGraph, procGraph, queueBuilder, simulator);
        
        this.processedTasks = new ArrayList<PreemptiveStrategy.Pair>();
        
        final Simulator sim = new Simulator(simBundle, new NeighbourStrategyFactory(), new SimpleRouterFactory());
        final SimulationResult res = sim.start();
        final List<TimeLine> procsHist = res.getProcHistories();
        taskPlan = new HashMap<Node, Node>();
        
        for (TimeLine procLine : procsHist) {
            for (HistoryBean bean : procLine.getIntervals()) {
                if (bean != null) {
                    taskPlan.put(((ProcHistoryBean) bean).getNode(), (Node) procLine.getTitleObject());
                }
            }
        }
    }

    @Override
    public void makeStep() {
        final List<Node> nodes = getReadyNodes();
        
        for (Node node : nodes) {
            final Node procNode = taskPlan.get(node);
            final Processor proc = simulator.getCorrespondProcessor(procNode);
            if (!proc.isBusy()) {
                // collect all predecessors
                final Set<Integer> predecessors = new HashSet<Integer>();
                for (Node pred : taskGraph.getPredecessors(node)) {
                    predecessors.add(pred.getId());
                }
                proc.setTask(new Task(node, predecessors));
                processingTasks.put(node, proc);
            }
        }

        final Iterator<Pair> iter = processedTasks.iterator();
        
        while (iter.hasNext()) {
            final Pair pair = iter.next();
            final Task task = pair.task;
            final Processor procFrom = pair.proc;
            
            for (Node node : taskGraph.getSuccessors(task.getNode())) {
                final Edge edge = getEdgeBetweenAandB(task.getNode(), node);
                if (edge == null) {
                    System.out.println("AAAA");
                }
    
                final Processor procTo = simulator.getCorrespondProcessor(taskPlan.get(node));
                final Message msg = new Message(edge.getWeight(), task.getId(), node.getId(), procTo, procFrom);
                procFrom.receiveMessage(msg);
            }
            
            iter.remove();
        }
    }
    
    @Override
    public void taskEnded(Processor proc, Task task) {
        super.taskEnded(proc, task);
        processedTasks.add(new Pair(proc, task));
    }
    
    private class Pair {
        Processor proc; Task task;

        public Pair(Processor proc, Task task) {
            this.proc = proc;
            this.task = task;
        }
    }
    
    public static class PreemptiveStrategyFactory implements StrategyFactory {
        private final SimulationBundle simBundle;
        
        public PreemptiveStrategyFactory(final SimulationBundle simBundle) {
            this.simBundle = simBundle;
        }
        
        public Strategy createStrategy(
                final Graph<Node, Edge> taskGraph,
                final Graph<Node, Edge> procGraph, 
                final QueueBuilder queueBuilder,
                final Simulator simulator) {
            return new PreemptiveStrategy(simBundle, taskGraph, procGraph, queueBuilder, simulator);
        }
    }
}
